package com.grt.condify.reflect;

import com.grt.condify.common.filter.IFilter;
import com.grt.condify.utils.StringUtil;
import io.vavr.control.Option;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.grt.condify.utils.PreConditionCheck.checkArgument;
import static com.grt.condify.utils.PreConditionCheck.checkNotNull;

/**
 * Created by cy on 2015/8/29.
 * <p>
 * 通用的Class工具，主要提供了一些反射中同Class相关的内容
 */
public class ClassUtils {

    private static final Map<Class<?>, Class<?>> PRIMITIVE_DATA_TYPE = new HashMap<Class<?>, Class<?>>();

    static {
        PRIMITIVE_DATA_TYPE.put(byte.class, Byte.class);
        PRIMITIVE_DATA_TYPE.put(short.class, Short.class);
        PRIMITIVE_DATA_TYPE.put(int.class, Integer.class);
        PRIMITIVE_DATA_TYPE.put(long.class, Long.class);
        PRIMITIVE_DATA_TYPE.put(float.class, Float.class);
        PRIMITIVE_DATA_TYPE.put(double.class, Double.class);
        PRIMITIVE_DATA_TYPE.put(char.class, Character.class);
        PRIMITIVE_DATA_TYPE.put(String.class, String.class);
        PRIMITIVE_DATA_TYPE.put(boolean.class, Boolean.class);
    }

    private ClassUtils() {
    }

    public static boolean isPrimitiveType(Class clazz) {
        if (clazz == null) {
            return false;
        }
        return PRIMITIVE_DATA_TYPE.containsKey(clazz) || PRIMITIVE_DATA_TYPE.containsValue(clazz);
    }

    /**
     * 将基本类型转换成其对应的完整类型
     *
     * @param clazz
     */
    public static Class getPrimitiveTypeClass(Class clazz) {
        checkNotNull(clazz);
        if (isPrimitiveType(clazz) && PRIMITIVE_DATA_TYPE.containsKey(clazz)) {
            return PRIMITIVE_DATA_TYPE.get(clazz);
        }
        return null;
    }



    /**
     * judge {@code clazz} is compatible with {@code referenceClass}. Compatible contains these behavior : equals, superclass, superinterface
     *
     * @param clazz
     * @param referenceClass if it's not interface , this method will judge the compatible with force convertion.
     * @return true if compatible
     */
    public static boolean isCompatible(Class<?> clazz, Class<?> referenceClass) {
        checkNotNull(clazz, "original class is null.");
        checkNotNull(referenceClass, "The reference is empty . ");
        if (clazz == referenceClass) {
            return true;
        }
        if (clazz.getName().equalsIgnoreCase(referenceClass.getName())) {
            return true;
        }
        try {
            if (referenceClass.isAssignableFrom(clazz))    // if referenceClass is super class or super interface of clazz , return true;
            {
                return true;
            }
        } catch (Exception ex) {
            return false;
        }

        return false;
    }

    /**
     * judgement accessible field whether it has parameterized type.
     *
     * @param field
     * @return
     */
    public static boolean hasGenericType(Field field) {
        checkNotNull(field);
        try {
            Type genericType = field.getGenericType();    //if field is primitive, itself type will be return.

            if (genericType == null) {
                return false;
            }
//			if(field.getType()==genericType)
//				return false;
            if (!(genericType instanceof ParameterizedType)) {
                return false;
            }

        } catch (Exception ex) {
            return false;
        }
        return true;
    }

    /**
     * get parameterized type class of accessible field
     * if accessible field is not parameterized field, return null.
     *
     * @param field
     * @return
     */
    public static Class<?> getSingleGenericType(Field field) {
        checkNotNull(field);
        if (!hasGenericType(field))    // not generic type return null;
        {
            return null;
        }
        Type genericType = field.getGenericType();
        ParameterizedType pType = (ParameterizedType) genericType;
        Type[] results = pType.getActualTypeArguments();
        checkArgument(results.length == 1, "%s has more than one parameterized type . ", field.getName());
        return (Class<?>) results[0];
    }

    /**
     * 解析获得默认的构造函数,如果构造函数参数长度为0则认为是默认的构造函数
     *
     * @param clazz
     * @return
     * @throws RuntimeException
     */
    public static Constructor getDefaultConstructor(Class<?> clazz) throws SecurityException {

        checkNotNull(clazz);
        checkArgument(!(clazz.isAnnotation() || clazz.isInterface()), "%s does have contructor .", clazz.getName());

        Constructor[] constructors = clazz.getDeclaredConstructors();
        for (Constructor one : constructors) {
            if (one.getGenericParameterTypes().length == 0) {
                if (canAccessPrivateMethod()) {
                    try {
                        one.setAccessible(true);
                    } catch (SecurityException ex) {
                        throw new SecurityException(
                                String.format("[%s] although constructor can access private method, but still error when set accessible to true ! Error Info : [%s]",
                                        clazz.getName(), ex.toString()));
                    }
                }
                if (one.isAccessible()) {
                    return one;
                }
            }
        }
        return null;
    }

    @Deprecated
    public static Constructor addDefaultConstructor(Class<?> clazz) throws SecurityException {
        return getDefaultConstructor(clazz);
    }

    private static boolean canAccessPrivateMethod() {
        SecurityManager securityManager = System.getSecurityManager();
        if (securityManager != null) {
            try {
                securityManager.checkPermission(new ReflectPermission("suppressAccessChecks")); //检查是否能够通过反射访问方法而不受访问控制
            } catch (SecurityException sex) {
                return false;
            }
        }
        return true;
    }

    /**
     * 得到默认的类加载器，如果返回为null，表示未通过线程与类本身，甚至系统自身类加载器获得
     *
     * @return 返回可用的默认类加载器
     */
    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
        try {
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Exception e) {
            //can not fetch it from thread context....
        }
        if (cl == null) {

            try {
                cl = ClassUtils.class.getClassLoader();
            } catch (Exception e) {
                // can not fetch it by classloader of current class
            }
            if (cl == null) {
                try {
                    cl = ClassLoader.getSystemClassLoader();
                } catch (Exception e) {
                    // shit .... maybe the program is error
                }
            }
        }
        return cl;
    }

    /**
     * 取代JDK默认的代理方式，该方式使用更加人性化并且会对传入参数进行检查，避免出现空指针异常
     *
     * @param interfaceType 被代理的接口
     * @param handler       InvocationHandler 实现
     * @return 接口对应的代理实例
     */
    public static <T> T newProxy(Class<T> interfaceType, InvocationHandler handler) {

        checkNotNull(interfaceType);
        checkNotNull(handler);
        checkArgument(interfaceType.isInterface(), "%s is not a interface type !", interfaceType.getName());
        Object instance = Proxy.newProxyInstance(getDefaultClassLoader(), new Class<?>[]{interfaceType}, handler);
        return interfaceType.cast(instance);
    }

    /**
     * 解析Class获得所有满足拦截条件的字段.该方法会对{@code clazz}父类进行相同工作.<p/>
     * 当{@code filter}为空时,该类所有属性将返回
     *
     * @param clazz
     * @param filter
     * @return
     */
    public static List<Field> getAllFieldsWithFilter(Class<?> clazz, IFilter<Field> filter) {

        List<Field> lstSelectedFields = new ArrayList<>();
        for (Field oneField : clazz.getDeclaredFields()) {
            if (canAccessPrivateMethod()) {
                try {
                    oneField.setAccessible(true);
                } catch (SecurityException sex) {
                    //
                }
            }
            if (oneField.isAccessible()) {
                //对字段进行拦截,确保接收的字段包含Setter和Getter或包含Annotation
                Field filtedField = null;
                if (filter != null) {
                    filter.doFilter(oneField);
                    if (filter.isHit("")) {
                        Field hitField = filter.getHitFilter();
                        filtedField = hitField == oneField ? hitField : null; //避免命中以后之后反复出现相同的命中字段
                    }
                } else {
                    filtedField = oneField;
                }
                if (filtedField == null) {
                    continue;
                } else {
                    lstSelectedFields.add(filtedField);
                }
            }
        }
        //对父类进行字段的获得
        if (clazz.getSuperclass() != null) {
            lstSelectedFields.addAll(getAllFieldsWithFilter(clazz.getSuperclass(), filter));
        }
        return lstSelectedFields;
    }

    /**
     * 根据字段名称获得
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    public static Option<Field> getFieldByName(Class<?> clazz, final String fieldName) {
        if (StringUtil.isEmpty(fieldName)) {
            return Option.none();
        } else {
            IFilter<Field> filterByName = new IFilter<Field>() {

                private Field hitOne = null;
                private boolean isHit = false;

                @Override
                public boolean isHit(String currentContent) {
                    return isHit;
                }

                @Override
                public boolean isHit(Class<?> currentContent) {
                    return isHit;
                }

                @Override
                public Field getHitFilter() {
                    return hitOne;
                }

                @Override
                public void doFilter(Field currFilter) throws RuntimeException {
                    if (currFilter.getName().equalsIgnoreCase(fieldName)) {
                        this.hitOne = currFilter;
                        this.isHit = true;
                    }
                }
            };
            List<Field> result = getAllFieldsWithFilter(clazz, filterByName);
            return (result == null || result.isEmpty()) ? Option.none() : Option.of(result.get(0));
        }
    }

    /**
     * 查找{@code clazz}中是否包含方法名为{@code method},参数列表为{@code parameterTypes}的方法
     *
     * @param clazz
     * @param method
     * @param parameterTypes
     * @return
     */
    public static Option<Method> getMethodIncludingSupers(Class<?> clazz, String method, Class<?>... parameterTypes) {
        try {
            Option<Method> resultMethod = Option.of(checkNotNull(clazz).getDeclaredMethod(checkNotNull(method), parameterTypes));
            return resultMethod;
        } catch (NoSuchMethodException | SecurityException ex) { //如果当前类中不存在指定的方法,则到父类中进行查找
            Class<?> superClass = clazz.getSuperclass();
            return superClass == null ? Option.none() : getMethodIncludingSupers(superClass, method, parameterTypes);
        }
    }


}
